package com.gitee.wsl.text.reader.line

import kotlin.math.max


open class LookaheadText(private val text: CharSequence) {
    private val lines: List<String> = text.split('\n')

    val startPosition: Position? = if (text.isNotEmpty())
        Position(0, -1, -1).nextPosition()
    else
        null

    private fun calcGlobalPos(lineN: Int, localPos: Int): Int {
        if (lineN < 0 || lineN >= lines.size)
            return NO_SET
        var globalPos = 0
        for (i in 0 until lineN) {
            globalPos += lines[i].length + 1
        }
        val localPos = when(localPos){
            //EOL_INDEX-> lines[lineN].length
            NO_SET -> 0
            else-> localPos
        }
        return globalPos + localPos
    }

    inner class Position internal constructor(private val lineN: Int,
                                              private val localPos: Int, // -1 if on newline before
                                              private val globalPos: Int = calcGlobalPos(lineN, localPos)) : Comparable<Position>{

        val originalText: CharSequence get() = text

        val currentLineText = lines[lineN]

        val nextLineText: String?
            get() = if (lineN + 1 < lines.size) {
                lines[lineN + 1]
            } else {
                null
            }

        val prevLineText: String?
            get() = if (lineN > 0) {
                lines[lineN - 1]
            } else {
                null
            }

        val char: Char
            get() = text[globalPos]

        init {
            require(localPos >= NO_SET && localPos < currentLineText.length)
        }

        override fun toString(): String {
            return "Position: '${
                if (localPos == NO_SET) {
                    "\\n$currentLineText"
                } else {
                    currentLineText.substring(localPos)
                }
            }'"
        }

        val offset: Int
            get() = globalPos

        val offsetInCurrentLine: Int
            get() = localPos

        val toLineEndLength :Int
            get() = currentLineText.length - localPos

        val nextLineOffset: Int?
            get() = if (lineN + 1 < lines.size) {
                globalPos + toLineEndLength
            } else {
                null
            }

        val prevLineOffset: Int?
            get() = if (lineN > 0) {
                globalPos - localPos - prevLineText!!.length
            } else {
                null
            }

        val hasNextLine:Boolean get() = nextLineOffset != null

        val nextLineOrEofOffset: Int
            get() = globalPos + toLineEndLength

        val isStartOfLine:Boolean
            get() = offsetInCurrentLine == NO_SET

        val isEndOfLine:Boolean
            get() = offsetInCurrentLine == NO_SET || charsToNonWhitespace() == null

        val textFromPosition: CharSequence
            get() = text.subSequence(globalPos, text.length)

        val currentLineTextFromPosition: CharSequence
            get() = currentLineText.substring(offsetInCurrentLine)

        fun charsToNonWhitespace(): Int? {
            val line = currentLineText
            var offset = max(localPos, 0)
            while (offset < line.length) {
                val c = line[offset]
                if (c != ' ' && c != '\t') {
                    return offset - localPos
                }
                offset++
            }
            return null
        }


        operator fun plus(length: Int): Position {
            return Position(this.lineN, this.localPos + length,globalPos + length)
        }


        fun nextPosition(delta: Int = 1): Position? {
            var remaining = delta
            var currentPosition = this

            while (true) {
                if (remaining == 0) {
                    return currentPosition
                }
                if (currentPosition.localPos + remaining < currentPosition.currentLineText.length) {
                    return Position(currentPosition.lineN,
                        currentPosition.localPos + remaining,
                        currentPosition.globalPos + remaining)
                } else {
                    /*val nextLine = currentPosition.nextLineOffset
                    if (nextLine == null) {
                        return null
                    } else {
                        val payload = currentPosition.toLineEndLength

                        currentPosition = Position(currentPosition.lineN + 1, -1, currentPosition.globalPos + payload)
                        remaining -= payload
                    }*/

                    if(hasNextLine){
                        remaining -= currentPosition.toLineEndLength
                        currentPosition = nextLinePosition()!!
                    } else
                        return null
                }
            }
        }

        fun nextLinePosition(): Position? {
            /*val nextLine = nextLineOffset
                ?: return null
            return nextPosition(nextLine - offset)*/

           return if( hasNextLine ){
                Position(lineN + 1, NO_SET, nextLineOrEofOffset)
            } else
                 null
        }

        fun lineEndPosition(): Position = this + toLineEndLength

        fun lineStartPosition(): Position = Position(lineN, 0)

        fun prevLineEndPosition(): Position? = prevLinePosition()?.lineEndPosition()

        fun prevLinePosition(): Position? {
            /*val nextLine = nextLineOffset
                ?: return null
            return nextPosition(nextLine - offset)*/

            return if(lineN > 0 ){
                Position(lineN - 1, NO_SET, prevLineOffset!!)
            } else
                null
        }

        override fun compareTo(other: Position): Int {
             if (lineN == other.lineN) {
                    return this.localPos - other.localPos
                }
            return this.lineN - other.lineN
        }

        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null) return false
            if (other::class != this::class) return false

            other as Position

            return globalPos == other.globalPos
        }

        override fun hashCode() = globalPos

        /**
         * Computes whether this point comes strictly before another point.
         * @param other the other point
         */
        fun isBefore(other: Position) = this < other

        /**
         * Computes whether this point is the same as, or comes before, another point.
         * @param other the other point
         */
        fun isSameOrBefore(other: Position) = this <= other

        /**
         * Computes whether this point is the same as, or comes after, another point.
         * @param other the other point
         */
        fun isSameOrAfter(other: Position) = this >= other


    }

    fun lineStart(line: Int): Position = Position(line, 0)

    fun lineEnd(line: Int): Position = Position(line, lines[line].length - 1)

    companion object{
        const val NO_SET = -1

        /**
         * To avoid computing the line size, we use a very large number that will hopefully be reached by the line size.
         *
         * We do not use `Int.MAX_VALUE` directly to avoid possible overflows in operations on the client side.
         */
        //const val EOL_INDEX: Int = Int.MAX_VALUE / 4

    }

}


